# 鸢尾花分类
# 通过对鸢尾花分类来了解机器学习的基本步骤
import mglearn
import numpy as np
import pandas as pd
# 从Pandas 0.25版本开始，scatter_matrix函数被移动到了pandas.plotting模块中。

# 加载数据集
from sklearn.datasets import load_iris
iris_dataset = load_iris()

# load_iris 返回的 iris 对象是一个 Bunch 对象，与字典非常相似，里面包含键和值
print(f"Keys of iris_dataset: \n{iris_dataset.keys()}")

# DESCR 键对应的值是数据集的简要说明。我们这里给出说明的开头部分
print(iris_dataset['DESCR'][:193] + "\n...")

# target_names 键对应的值是一个字符串数组，里面包含我们要预测的花的品种：
print(f"Target names: {iris_dataset['target_names']}")

# feature_names 键对应的值是一个字符串列表，对每一个特征进行了说明：
print(f"Feature names: \n{iris_dataset['feature_names']}")

# 数据包含在 target 和 data 字段中。data 里面是花萼长度、花萼宽度、花瓣长度、花瓣宽
# 度的测量数据，格式为 NumPy 数组
print(f"Type of data: {type(iris_dataset['data'])}")

# data 数组的每一行对应一朵花，列代表每朵花的四个测量数据：
print(f"Shape of data: {iris_dataset['data'].shape}")
# 结果 Shape of data: (150, 4)
# 可以看出，数组中包含 150 朵不同的花的测量数据。前面说过，机器学习中的个体叫作样本（sample），其属性叫作特征（feature）。
# data 数组的形状（shape）是样本数乘以特征数。这是 scikit-learn 中的约定，你的数据形状应始终遵循这个约定。
print(f"First five rows of data:\n{iris_dataset['data'][:5]}")

# 从数据中可以看出，前 5 朵花的花瓣宽度都是 0.2cm，第一朵花的花萼最长，是 5.1cm。
# target 数组包含的是测量过的每朵花的品种，也是一个 NumPy 数组：
# target 是一维数组，每朵花对应其中一个数据：
print(f"Shape of target: {iris_dataset['target'].shape}")
# Shape of target: (150,)

# 品种被转换成从 0 到 2 的整数：
print(f"Target:\n{iris_dataset['target']}")

'''
Target:
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
'''

'''
上述数字的代表含义由 iris['target_names'] 数组给出：0 代表 setosa，1 代表 versicolor，2 代表 virginica。

衡量模型是否成功：训练数据与测试数据
要将数据集分为训练集和测试集防止泛化

scikit-learn 中的 train_test_split 函数可以打乱数据集并进行拆分。这个函数将 75% 的
行数据及对应标签作为训练集，剩下 25% 的数据及其标签作为测试集。训练集与测试集的
分配比例可以是随意的，但使用 25% 的数据作为测试集是很好的经验法则。

scikit-learn 中的数据通常用大写的 X 表示，而标签用小写的 y 表示。这是受到了数学
标准公式 f(x)=y 的启发，其中 x 是函数的输入，y 是输出。我们用大写的 X 是因为数据是
一个二维数组（矩阵），用小写的 y 是因为目标是一个一维数组（向量），这也是数学中的约定。

'''

# 对数据调用 train_test_split，并对输出结果采用下面这种命名方法：
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    iris_dataset['data'], iris_dataset['target'], random_state=0
)

'''
在对数据进行拆分之前，train_test_split 函数利用伪随机数生成器将数据集打乱。
这里将数据打乱，是为了确保测试集中包含所有类别的数据。

为了确保多次运行同一函数能够得到相同的输出，我们利用 random_state 参数指定了随机数生成器的种子。
这样函数输出就是固定不变的，所以这行代码的输出始终相同。本书用到随机过程时，都会用这种方法指定 random_state。
'''

# X_train 包含 75% 的行数据，X_test 包含剩下的 25%
print(f'X_train shape: {X_train.shape}')
print(f'y_train shape: {y_train.shape}')
# X_train shape: (112, 4)
# y_train shape: (112,)

print(f"X_test shape: {X_test.shape}")
print(f"y_test shape: {y_test.shape}")

'''
要事第一：观察数据
在构建机器学习模型之前，通常最好检查一下数据，看看如果不用机器学习能不能轻松完成任务，
或者需要的信息有没有包含在数据中。

此外，检查数据也是发现异常值和特殊值的好方法。举个例子，可能有些鸢尾花的测量单
位是英寸而不是厘米。在现实世界中，经常会遇到不一致的数据和意料之外的测量数据。

检查数据的最佳方法之一就是将其可视化。一种可视化方法是绘制散点图（scatter plot）。
数据散点图将一个特征作为 x 轴，另一个特征作为 y 轴，将每一个数据点绘制为图上的一个点。

但是这样一次只能绘制两个特征
用这种方法难以对多于 3 个特征的数据集作图。解决这个问题的一种方法是绘制散点图矩阵（pair plot），
从而可以两两查看所有的特征。
'''

# 利用X_train中的数据创建DataFrame
# 利用iris_dataset.feature_names中的字符串对数据列进行标记
iris_dataframe = pd.DataFrame(X_train, columns=iris_dataset['feature_names'])
# 利用DataFrame创建散点图矩阵，按y_train着色
# pd.plotting.scatter_matrix(iris_dataframe, ...): 这是一个用于生成散点图矩阵的函数。
# c=y_train: 这个参数指定了每个点的颜色。y_train 是一个数组，包含了训练数据集中每朵鸢尾花的类别标签。
# figsize=(15,15): 这个参数设置了生成的图形的大小，单位是英寸。
# marker='o': 这个参数指定了散点图中点的形状
# hist_kwds={'bins':20}: 这个参数用于设置散点图矩阵中对角线上的直方图的参数
# s=60: 这个参数指定了散点图中点的大小。
# alpha=.8: 这个参数指定了点的透明度。
# cmap=mglearn.cm3: 这个参数指定了颜色映射（colormap）。mglearn.cm3 是一个颜色映射表，用于将类别标签映射到不同的颜色。
grr = pd.plotting.scatter_matrix(iris_dataframe,c=y_train,figsize=(15,15),marker='o',hist_kwds={'bins':20},s=60,alpha=.8,cmap=mglearn.cm3)
# 显示图表
# plt.show()

'''
构建第一个模型：k近邻算法

在k近邻算法中，k的值表示我们在训练集中选择与新数据点最近的k个邻居进行考虑.
例如，可以选择距离最近的3个或5个邻居，而不仅仅局限于最近的一个邻居。
接着，我们根据这些邻居中出现次数最多的类别来进行预测.目前，我们只考虑一个邻居的情况
'''


# 首先从scikit-learn中引入k 近邻分类算法
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=1)

'''
KNN对象封装了k近邻算法的整个流程，既包括使用训练数据构建模型的过程，也包括对新数据点进行预测的过程。
此外，它还保存了从训练数据中提取的关键信息。对于KNeighborsClassifier而言，它主要存储了训练集的数据.

要基于训练集构建模型，需要调用KNN对象的fit方法，其输入参数为X_train和y_train。
这两个参数都是NumPy数组，其中X_train包含了训练数据，y_train则包含了相应的训练标签：
'''


knn.fit(X_train, y_train)

# 从中可以看出构建模型时用到的参数。几乎所有参数都是默认值，但你也会注意到 n_neighbors=1，这是我们传入的参数。
# scikit-learn 中的大多数模型都有很多参数，但多用于速度优化或非常特殊的用途。

# algorithm='auto': 指定用于计算最近邻的算法。'auto' 表示根据数据集的大小和特征数量自动选择最合适的算法
# leaf_size=30: 当使用 'ball_tree' 或 'kd_tree' 算法时，这个参数指定了树的叶子节点中包含的样本数量。
# metric='minkowski': 指定用于计算样本之间距离的度量方式。
# metric_params=None: 这个参数用于传递给距离度量函数的额外参数。
# n_jobs=1: 指定用于计算的 CPU 核心数。1 表示使用一个 CPU 核心进行计算。如果设置为 -1，则使用所有可用的 CPU 核心
# n_neighbors=1: 指定在进行预测时考虑的最近邻居的数量。
# p=2: 当 metric='minkowski' 时，这个参数指定了闵可夫斯基距离的参数。2 表示使用欧氏距离.
# weights='uniform': 指定在进行预测时如何加权邻居的贡献。'uniform' 表示所有邻居的贡献是相同的，即不考虑邻居的距离远近
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',metric_params=None, n_jobs=1,n_neighbors=1,p=2,weights='uniform')

'''
做出预测
现在我们可以使用这个模型来预测新数据的标签了，尽管我们可能并不知道这些新数据的真实标签。
例如，假设我们在野外发现了一朵鸢尾花，其花萼长度为5cm，宽度为2.9cm，花瓣长度为1cm，宽度为0.2cm。
我们想知道这朵鸢尾花属于哪个品种。

为了进行预测，我们可以将这些数据放入一个NumPy数组中，并确保数组的形状为样本数（1）乘以特征数（4）：

'''

X_new = np.array([[5,2.9,1,0.2]])
print(f"X_new.shape: {X_new.shape}")
# X_new.shape: (1, 4)
# 注意，我们将这朵花的测量数据转换为二维 NumPy 数组的一行，
# 这是因为 scikit-learn的输入数据必须是二维数组。

# 调用 knn 对象的 predict 方法来进行预测：
prediction = knn.predict(X_new)
print(f"Prediction: {prediction}")
print(f"Prediction target name: {iris_dataset['target_names'][prediction]}")

'''
预测后就要评估模型

评估模型
确实如此，我们可以利用之前创建的测试集来评估模型的性能。
测试集中的数据没有参与模型的训练过程，但我们知道每朵鸢尾花的实际品种，因此可以作为验证模型预测准确性的基准。

具体操作时，我们对测试集中的每朵鸢尾花进行预测，并将预测结果与已知的品种标签进行对比。
通过计算模型的精度（accuracy），我们可以了解模型的预测性能。
精度是指预测正确的鸢尾花品种占总测试样本的比例，是衡量分类模型优劣的一个重要指标：
'''


y_pred = knn.predict(X_test)
print(f"Test set predictions:\n{y_pred}")
# np.mean计算数组中元素的平均值
print("Test set score: {:.2f}".format(np.mean(y_pred == y_test)))

'''
对于这个模型来说，测试集的精度约为 0.97，也就是说，对于测试集中的鸢尾花，我们的
预测有 97% 是正确的。根据一些数学假设，对于新的鸢尾花，可以认为我们的模型预测
结果有 97% 都是正确的。对于我们的植物学爱好者应用程序来说，高精度意味着模型足
够可信，可以使用。在后续章节中，我们将讨论提高性能的方法，以及模型调参时的注意事项。
'''

'''
总结 机器学习的步骤
分割 数据集和测试集 split
声明要使用的算法或模型 fit
进行预测 predict
进行准确度评估 score
'''


